﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;

namespace Bystd.Geo.GeoParsers
{
    using Bystd.Geo.Geometries;
    using Converters;

    public class TiffParser : BaseFileParser
    {
        private BitExtensions bitConvert = null;

        public TiffParser(string geoTiff)
            :base(geoTiff)
        {
           
        }

        public TiffInfo FromReader()
        {
            var TiffInfo = new TiffInfo()
            {
                FHeader = new IFH(),
                FDirectory = new List<IFD>(2)
            };
            using (BinaryReader reader = new BinaryReader(File.OpenRead(filename),encoding))
            {
                TiffInfo.FHeader.OrderFlag = reader.ReadUInt16();
                ByteOrder byteOrder = TiffInfo.FHeader.OrderFlag == 0x4949 ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
                bitConvert = new BitExtensions(byteOrder);
                //header
                TiffInfo.FHeader.TiffFlag = (UInt16)bitConvert.ToInt16(reader);
                TiffInfo.FHeader.IFDOffset = (UInt32)bitConvert.ToInt32(reader);
                uint offset = TiffInfo.FHeader.IFDOffset;

            again:
                IFD iFD = new IFD();

                if (offset > 0)
                {
                    //定位偏移地址
                    reader.BaseStream.Seek(offset, SeekOrigin.Begin);
                }

                //directory
                iFD.DECount = (UInt16)bitConvert.ToInt16(reader);

                //IFD
                iFD.FDAttribute = DecodeIFD(iFD, reader);

                iFD.NextOffset = (UInt32)bitConvert.ToInt32(reader);

                TiffInfo.FDirectory.Add(iFD);

                if (iFD.NextOffset > 0)
                {
                    offset = iFD.NextOffset;
                    goto again;
                }
            }
            return TiffInfo;
        }

        private FDAttribute DecodeIFD(IFD iFd,BinaryReader  reader)
        {
            //DE
            iFd.DE = new DE[iFd.DECount];
            FDAttribute attr = new FDAttribute();

            for (int i = 0; i < iFd.DECount; ++i)
            {
                var de = new DE()
                {
                    Tag = bitConvert.ToInt16(reader),
                    DType = (DataType)bitConvert.ToInt16(reader),
                    Count = bitConvert.ToInt32(reader),
                    ValueOffset = bitConvert.ToInt32(reader)
                };

                switch (de.Tag)
                {
                    case 0x0100:
                            attr.With = de.ValueOffset;
                        break;
                    case 0x0101:
                            attr.Height = de.ValueOffset;
                        break;
                    case 0x0102:
                            attr.Depth = de.ValueOffset;
                        break;
                    case 0x0103:
                            attr.IsCompression = de.ValueOffset == 5 ? true : false;
                        break;
                    case 0x0106:
                            attr.IsAntiColor = de.ValueOffset == 1 ? true : false;
                        break;
                    case 0x0111:
                            attr.ScanOffset = de.ValueOffset;
                        break;
                    case 0x0116:
                            attr.ScanCount = de.ValueOffset;
                        break;
                    case 0x0117:
                            attr.TotalLength = de.ValueOffset;
                        break;
                    case 0x011A:
                        {
                            reader.BaseStream.Seek(de.ValueOffset, SeekOrigin.Begin);
                            float s = bitConvert.ToInt16(reader)*1f;
                            uint b = (UInt32)bitConvert.ToInt32(reader);
                            attr.HResolution = b > 0 ? (s / b) : 0;
                        }
                        break;
                    case 0x011B:
                        {
                            reader.BaseStream.Seek(de.ValueOffset, SeekOrigin.Begin);
                            float s= bitConvert.ToInt16(reader)*1f;
                            uint b = (uint)bitConvert.ToInt32(reader);
                            attr.VResolution = b > 0 ? s / b : 0;
                        }
                        break;
                    case 0x0128:
                        attr.ResolutionUnit = (ushort)de.ValueOffset;
                        break;
                    case 0x0131:
                        {
                            byte[] t = reader.ReadBytes(de.Count);
                            if (bitConvert.ByteOrder==ByteOrder.BigEndian) t = t.Reverse().ToArray();

                            attr.Source = Encoding.Default.GetString(t);
                        }
                        break;
                    case 0x0132:
                        {
                            byte[] t = reader.ReadBytes(de.Count);
                            if (bitConvert.ByteOrder == ByteOrder.BigEndian) t = t.Reverse().ToArray();

                            attr.Date = Encoding.Default.GetString(t);
                        }
                        break;
                    case 0x0140:
                        {
                            attr.PlatteOffset = de.ValueOffset;
                        }
                        break;
                }

                iFd.DE[i] = de;
            }
            return attr;
        }
    }

    public class TiffInfo
    {
        /// <summary>
        /// 文件头
        /// </summary>
        public IFH FHeader { get; set;}
        /// <summary>
        /// 文件目录
        /// </summary>
        public List<IFD> FDirectory { get; set; }
    }
    /// <summary>
    /// 文件头
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public class IFH
    {
        /// <summary>
        /// 字节顺序标志位(18761)ii,小端字节序;(19789)mm,大端字节序
        /// </summary>
        public UInt16 OrderFlag { get; set; }
        /// <summary>
        /// Tiff标志位
        /// </summary>
        public UInt16 TiffFlag { get; set; }
        /// <summary>
        /// tiff偏移量
        /// </summary>
        public UInt32 IFDOffset { get; set; }
    }

    /// <summary>
    /// 目录
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public class IFD
    {
        public UInt16 DECount { get; set; }//DE个数n
        public DE[] DE { get; set; }//目录项
        public UInt32 NextOffset { get; set; }//下一个IFD偏移

        public FDAttribute FDAttribute { get; set; }//详细属性
    }

    [StructLayout(LayoutKind.Sequential)]
    public class DE
    {
        public Int16 Tag { get; set; }//tag唯一标识

        public DataType DType { get; set; }//数据类型

        public int Count { get; set; }//通过类型和数量可以确定存储此TAG的数据需要占据的字节数

        public int ValueOffset { get; set; }
    }
    [StructLayout(LayoutKind.Sequential)]
    public class FDAttribute
    {
        public int With { get; set; }

        public int Height { get; set; }

        public int Depth { get; set; }//1为单色，＝4为16色，＝8为256色。如果该类型数据个数＞2个，说明是真彩图像

        public bool IsCompression { get; set; }//值＝05表示压缩

        public bool IsAntiColor { get; set; }//值＝01表示反色，否则表示不反色

        public int ScanOffset { get; set; }//扫描偏移

        public int ScanCount { get; set; }//扫描数量

        public int TotalLength { get; set; }//如果不是偶数，那么实际存放时会在后面加0
        public float HResolution { get; set; }//水平分辨率偏移量，常用计量单位是：像素/英寸
        public float VResolution { get; set; }//垂直分辨率偏移量
        public string Source { get; set; }//生成图像来源
        public string Date { get; set; }//生成图像时间
        /// <summary>
        /// 256色和16色图像才有此属性，而且有连续2个调色板，但属性的length值只表示出1个调色板
        /// </summary>
        public int PlatteOffset { get; set; }//
        /// <summary>
        /// 单位， 1( 用于非四边形显示的图像 ),2( 英寸 ),3( 厘米 ) 
        /// </summary>
        public UInt16 ResolutionUnit { get; set; }
    }

    [Flags]
    public enum DataType:Int16
    {
        None=0,
        Byte=1,//
        Ascii=2,
        Short=3,//uint16
        Long=4,//uint32
        Rational=5,//two long
        Sbyte=6,
        Undefined=7,
        SShort=8,//int16
        SLong=9,//int32
        SRational=10,
        Float=11,
        Double=12
    }
}
